/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts, AstTreeUtils } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { FileMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InitDeclarationsCheck');
const gMetaData: BaseMetaData = {
  severity: 1,
  ruleDocPath: "docs/init-declarations-check.md",
  description: "equire or disallow initialization in variable declarations.",
};

// 定义一个接口，用于存储问题的行列信息
interface LocationInfo {
  fileName: string;
  line: number;
  character: number;
  description: string;
}
export class InitDeclarationsCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private defaultOptions = ['always'];

  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatcherCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check
    }
    return [fileMatcherCb];
  }

  public check = (arkFile: ArkFile) => {
    if (arkFile instanceof ArkFile) {
      const code = arkFile.getCode();
      if (!code) {
        return;
      }
      const filePath = arkFile.getFilePath();
      const asRoot = AstTreeUtils.getASTNode(arkFile.getName(), code);

      // 检查变量声明时进行初始化
      const LocationInfos = this.checkInitDeclarations(asRoot);

      // 输出结果
      LocationInfos.forEach(loc => {
        this.addIssueReportNode(loc, filePath);
      });
    }
  }

  private checkInitDeclarations(sourceFile: ts.SourceFile): LocationInfo[] {
    const option = this.rule && this.rule.option[0] ? this.rule.option : this.defaultOptions;
    const locationInfos: LocationInfo[] = [];

    const visit = (node: ts.Node) => {
      // 使用类型守卫确保 node 是 VariableDeclarationList 类型
      if (ts.isVariableDeclarationList(node)) {
        // 检查父节点是否为声明文件或 declare 声明
        const parentNode = node.parent as ts.Node;
        if (this.isDeclare(parentNode)) {
          return; // 如果是 declare 声明，则跳过
        }
      }

      // 检查是否为命名空间声明的子节点
      if (this.isAncestorNamespaceDeclared(node)) {
        return;
      }
      if (ts.isVariableDeclarationList(node)) {
        this.checkVariableDeclarationList(node, sourceFile, option, locationInfos);

      }
      ts.forEachChild(node, visit);
    };

    visit(sourceFile);
    return locationInfos;
  }

  private checkVariableDeclarationList(
    node: ts.VariableDeclarationList,
    sourceFile: ts.SourceFile,
    option: Object[],
    locationInfos: LocationInfo[]
  ): void {
    // 提取配置中的实际规则值
    const ruleConfig = option[0] ? option[0] : 'always';
    node.declarations.forEach((declarator) => {
      this.checkVariableDeclaration(declarator, ruleConfig, sourceFile, option, locationInfos);

    });
  }

  private checkVariableDeclaration(
    declarator: ts.VariableDeclaration,
    ruleConfig: Object,
    sourceFile: ts.SourceFile,
    option: Object[],
    locationInfos: LocationInfo[]
  ): void {

    if (ruleConfig === 'always') {
      // 检查是否在 for...in 或 for...of 循环中
      if (
        ts.isForInStatement(declarator.parent?.parent) ||
        ts.isForOfStatement(declarator.parent?.parent)
      ) {
        return;
      }
      if (!declarator.initializer) {
        const { line, character } = sourceFile.getLineAndCharacterOfPosition(declarator.getStart());
        locationInfos.push({
          fileName: sourceFile.fileName,
          line: line + 1,
          character: character + 1,
          description: `Variable '${declarator.name.getText()}' should be initialized on declaration.'`
        });
      }
    } else if (ruleConfig === 'never') {
      this.checkNeverExpression(declarator, sourceFile, option, locationInfos);
    }
  }

  private checkNeverExpression(
    declarator: ts.VariableDeclaration,
    sourceFile: ts.SourceFile,
    option: Object[],
    locationInfos: LocationInfo[]
  ): void {
    if (declarator.initializer) {
      // 检查是否为 const 声明
      const isConst = declarator.parent && ts.isVariableDeclarationList(declarator.parent) &&
        (declarator.parent.flags & ts.NodeFlags.Const) !== 0;
      if (!isConst) {
        this.checkNeverExpressionTwo(declarator, sourceFile, option, locationInfos);
      }
    }
  }

  private checkNeverExpressionTwo(
    declarator: ts.VariableDeclaration,
    sourceFile: ts.SourceFile,
    option: Object[],
    locationInfos: LocationInfo[]
  ): void {
    // 提取配置中的ignoreForLoopInit
    const ruleConfigForLoop = option[1] ? option[1] : '';
    if (typeof ruleConfigForLoop === 'object' && ruleConfigForLoop !== null && 'ignoreForLoopInit' in ruleConfigForLoop) {
      this.checkNeverExpressionObject(declarator, sourceFile, ruleConfigForLoop, locationInfos);
    } else {
      const { line, character } = sourceFile.getLineAndCharacterOfPosition(declarator.getStart());
      locationInfos.push({
        fileName: sourceFile.fileName,
        line: line + 1,
        character: character + 1,
        description: `Variable '${declarator.name.getText()}' should be initialized on declaration.'`
      });
    }
  }

  private checkNeverExpressionObject(
    declarator: ts.VariableDeclaration,
    sourceFile: ts.SourceFile,
    ruleConfigForLoop: Object & Record<"ignoreForLoopInit", unknown>,
    locationInfos: LocationInfo[]
  ): void {
    const ignoreForLoopInit = ruleConfigForLoop.ignoreForLoopInit;
    // 检查是否为 for 循环的初始化部分
    const isInForLoop = ts.isForStatement(declarator.parent?.parent) &&
      declarator.parent?.parent.initializer === declarator.parent;
    if (isInForLoop) {
      if (!ignoreForLoopInit) {
        const { line, character } = sourceFile.getLineAndCharacterOfPosition(declarator.getStart());
        locationInfos.push({
          fileName: sourceFile.fileName,
          line: line + 1,
          character: character + 1,
          description: `Variable '${declarator.name.getText()}' should be initialized on declaration.'`

        });
      }
    } else {
      const { line, character } = sourceFile.getLineAndCharacterOfPosition(declarator.getStart());
      locationInfos.push({
        fileName: sourceFile.fileName,
        line: line + 1,
        character: character + 1,
        description: `Variable '${declarator.name.getText()}' should be initialized on declaration.'`
      });
    }
  }

  // 辅助函数：判断节点是否为命名空间声明的子节点
  private isAncestorNamespaceDeclared(node: ts.Node): boolean {
    let current: ts.Node | undefined = node.parent;
    while (current) {
      if (ts.isModuleDeclaration(current) && current.name.kind === ts.SyntaxKind.Identifier) {
        return true;
      }
      current = current.parent;
    }
    return false;
  }

  private hasModifiers(node: ts.Node): node is ts.HasModifiers {
    return 'modifiers' in node;
  }

  private isDeclare(node: ts.Node): boolean {
    if (this.hasModifiers(node)) {
      return !!ts.getModifiers(node)?.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword);
    }
    return false;
  }

  private addIssueReportNode(info: LocationInfo, filePath: string): void {
    const severity = this.rule.alert ?? this.metaData.severity;
    if (info.description) {
      this.metaData.description = info.description;
    }
    let defect = new Defects(info.line, info.character, info.character, this.metaData.description, severity,
      this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
    this.issues.push(new IssueReport(defect, undefined));
    RuleListUtil.push(defect);
  }
}
